<?php

namespace NongShang;

use DomainException;
use Fize\Math\Financial;

/**
 * 厦农商租赁算法
 */
class NongShang
{

    /**
     * @var float 设备价格
     */
    protected $equipmentAmount;

    /**
     * @var float 加融款
     */
    protected $plusAmount;

    /**
     * @var float 首付款
     */
    protected $downPayment;

    /**
     * @var float 预付款
     */
    protected $deposit;

    /**
     * @var float 留购价
     */
    protected $residualValue;

    /**
     * @var float 手续费
     */
    protected $serviceAmount;

    /**
     * @var int 期次
     */
    protected $issues;

    /**
     * @var int 支付方式：0-期末支付；1-期初支付
     */
    protected $paymentMode;

    /**
     * @var int 每期月数
     */
    protected $issueMonths;

    /**
     * @var int 保留小数位
     */
    protected $scale;

    /**
     * @var array 还租计划(不含保证金抵扣)
     */
    protected $repayments;

    /**
     * @var float 转让价款
     */
    protected $leaseAmount;

    /**
     * @var float 概算本金
     */
    protected $principal;

    /**
     * @var array 还租计划
     */
    protected $plan;

    /**
     * @var float 外部年化利率(客户年化IRR)
     */
    protected $outsideRate;

    /**
     * @var float 内部年化利率(实际IRR)
     */
    protected $insideRate;

    /**
     * @var float 客户平面利率
     */
    protected $flatRate;

    /**
     * @var float 内部XIRR
     */
    protected $insideXirr;

    /**
     * 初始化
     * @param float $equipmentAmount 设备价格
     * @param float $plusAmount      加融款
     * @param float $downPayment     首付款
     * @param float $deposit         预付款
     * @param float $residualValue   留购价
     * @param float $serviceAmount   手续费
     * @param int   $issues          期次
     * @param int   $paymentMode     支付方式：0-期末支付；1-期初支付
     * @param int   $issueMonths     每期月数
     * @param int   $scale           保留小数位
     */
    public function __construct($equipmentAmount, $plusAmount, $downPayment, $deposit, $residualValue, $serviceAmount, $issues, $paymentMode = 0, $issueMonths = 1, $scale = 2)
    {
        $this->equipmentAmount = $equipmentAmount;
        $this->plusAmount = $plusAmount;
        $this->downPayment = $downPayment;
        $this->deposit = $deposit;
        $this->residualValue = $residualValue;
        $this->serviceAmount = $serviceAmount;

        $this->issues = $issues;
        $this->paymentMode = $paymentMode;
        $this->issueMonths = $issueMonths;
        $this->scale = $scale;

        $this->leaseAmount = round($equipmentAmount + $plusAmount, $scale);
    }

    /**
     * 设置还租计划
     *
     * 参数 $repayments ：
     * 从第0期开始，键名可以为空或者日期
     * @param array $repayments 每期租金组成的还租计划数组
     */
    public function setRepayments(array $repayments)
    {
        $this->repayments = $repayments;
    }

    /**
     * 获取还租计划
     * @return array
     */
    public function getPlan()
    {
        if (is_null($this->plan)) {
            $plan = [];
            $repaymentDates = array_keys($this->repayments);
            $repaymentAmounts = array_values($this->repayments);
            for ($i = 0; $i <= $this->issues; $i++) {
                $plan[$i]['issue'] = $i;
                if (is_string($repaymentDates[$i])) {
                    $plan[$i]['payment_date'] = $repaymentDates[$i];
                }
                if ($i == 0) {
                    $plan[0]['rent'] = $this->paymentMode == 1 ? $repaymentAmounts[$i] : 0.00;
                    $plan[0]['capital'] = $plan[0]['rent'];
                    $plan[0]['interest'] = 0;
                    $plan[0]['balance'] = round($this->getPrincipal() - $plan[0]['capital'], $this->scale);  // 剩余本金 = 概算本金 - 本金
                    $plan[0]['cash_flow'] = -round($this->leaseAmount - $this->downPayment - $this->deposit - $plan[0]['rent'] - $this->serviceAmount, $this->scale);  // 现金流 = -(转让价款 - 首付款 - 预付款 - 租前租金 - 手续费
                    $plan[0]['nominal_cash_flow'] = -$plan[0]['balance'];  // 名义现金流 = -剩余本金
                    $plan[0]['residual_value'] = 0;  // 留购价另算
                    $plan[0]['deposit_deduction'] = 0;  // 预付款抵扣另算
                    $plan[0]['payable_amount'] = $plan[0]['cash_flow'];  // 应付金额 = 转让价款 - 首付租金 - 预付款 - 租前租金 - 手续费
                } else {
                    $plan[$i]['rent'] = $this->paymentMode == 1 && $i == $this->issues ? 0.00 : $repaymentAmounts[$i];
                    // 以下3项先置0，另算
                    $plan[$i]['capital'] = 0;
                    $plan[$i]['interest'] = 0;
                    $plan[$i]['balance'] = 0;
                    // 以下2项此时仅算租金
                    $plan[$i]['cash_flow'] = ($this->paymentMode == 1 && $i == $this->issues) ? 0 : $repaymentAmounts[$i];  // 现金流 = 租金 - 预付款抵扣 + 留购
                    $plan[$i]['nominal_cash_flow'] = ($this->paymentMode == 1 && $i == $this->issues) ? 0 : $plan[$i]['rent'];  // 名义现金流 = 租金 + 留购
                    $plan[$i]['residual_value'] = 0;  // 留购价另算
                    $plan[$i]['deposit_deduction'] = 0;  // 预付款抵扣另算
                    $plan[$i]['payable_amount'] = 0;  // 应付金额另算
                }
            }
            // 预付款抵扣
            $start = $this->paymentMode == 1 ? $this->issues - 1 : $this->issues;
            $deposit = $this->deposit;
            for ($i = $start; $i > 0; $i--) {
                if ($deposit <= 0) {
                    break;
                }
                if ($plan[$i]['cash_flow'] <= $deposit) {
                    $plan[$i]['deposit_deduction'] = $plan[$i]['cash_flow'];
                    $plan[$i]['cash_flow'] = 0;
                } else {
                    $plan[$i]['deposit_deduction'] = $deposit;
                    $plan[$i]['cash_flow'] = round($plan[$i]['cash_flow'] - $deposit, $this->scale);
                }
                $deposit = round($deposit - $plan[$i]['deposit_deduction'], $this->scale);
            }
            // 留购价
            $plan[$this->issues]['residual_value'] = round($plan[$this->issues]['residual_value'] + $this->residualValue, $this->scale);
            $plan[$this->issues]['cash_flow'] = round($plan[$this->issues]['cash_flow'] + $this->residualValue, $this->scale);
            $plan[$this->issues]['nominal_cash_flow'] = round($plan[$this->issues]['nominal_cash_flow'] + $this->residualValue, $this->scale);

            $outsideIrr = $this->getOutsideIrr($plan);  // 外部IRR
            $end = $this->paymentMode == 1 ? $this->issues - 1 : $this->issues;
            for ($i = 1; $i <= $end; $i++) {
                if ($i == $end) {  // 最后一期先计算本金防止出现尾差
                    $capital = round($plan[$i - 1]['balance'] - $this->residualValue, $this->scale);
                    $interest = round($plan[$i]['rent'] - $capital, $this->scale);
                } else {
                    $interest = round($plan[$i - 1]['balance'] * $outsideIrr, $this->scale);
                    $capital = round($plan[$i]['rent'] - $interest, $this->scale);
                }
                $balance = round($plan[$i - 1]['balance'] - $capital, $this->scale);
                $plan[$i]['capital'] = $capital;
                $plan[$i]['interest'] = $interest;
                $plan[$i]['balance'] = $balance;
            }
            // 保存外部年利率
            $this->outsideRate = $outsideIrr * 12 / $this->issueMonths;
            for ($i = 1; $i <= $this->issues; $i++) {
                // 应付金额 = 租金 - 预付款抵扣 + 留购价
                $plan[$i]['payable_amount'] = round($plan[$i]['rent'] - $plan[$i]['deposit_deduction'] + $plan[$i]['residual_value'], $this->scale);
            }
            $this->plan = $plan;
        }

        return $this->plan;
    }

    /**
     * 获取外部年化利率(客户年化IRR)
     * @return float
     */
    public function getOutsideRate()
    {
        if (is_null($this->outsideRate)) {
            $this->getPlan();
        }
        return $this->outsideRate;
    }

    /**
     * 获取内部年化利率(实际年化IRR)
     * @return float
     */
    public function getInsideRate()
    {
        if (is_null($this->insideRate)) {
            $insideIrr = $this->getInsideIrr($this->getPlan());  // 内部IRR
            $this->insideRate = $insideIrr * 12 / $this->issueMonths;
        }
        return $this->insideRate;
    }

    /**
     * 获取客户平面利率
     * @return float
     */
    public function getFlatRate()
    {
        if (is_null($this->flatRate)) {
            $repaymentAmounts = array_values($this->repayments);
            $repaymentTotalAmount = array_sum($repaymentAmounts);
            // 分子 = 总租金 + 首付款 - 设备款 + 留购价
            $numerator = $repaymentTotalAmount  + $this->downPayment - $this->equipmentAmount + $this->residualValue;
            // 分母 = 设备款 - 首付款
            $denominator = $this->equipmentAmount - $this->downPayment;
            $this->flatRate = $numerator / $denominator / ($this->issues * $this->issueMonths) * 12;
        }
        return $this->flatRate;
    }

    /**
     * 获取内部XIRR
     * @return float
     */
    public function getInsideXirr()
    {
        if (is_null($this->insideXirr)) {
            $plan = $this->getPlan();
            $flow = [];
            $dates = [];
            for ($i = 0; $i <= $this->issues; $i++) {
                if (!isset($plan[$i]['payment_date'])) {
                    throw new DomainException("计算XIRR时，还款日期必须指定！");
                }
                $dates[$i] = $plan[$i]['payment_date'];
                $flow[$i] = $plan[$i]['cash_flow'];
            }
            $this->insideXirr = Financial::xirr($flow, $dates);
        }
        return $this->insideXirr;
    }

    /**
     * 概算本金
     *
     * 概算本金 = 设备价格 - 首付款
     * @return float
     */
    protected function getPrincipal()
    {
        if (is_null($this->principal)) {
            $this->principal = round($this->equipmentAmount - $this->downPayment, $this->scale);
        }
        return $this->principal;
    }

    /**
     * 获取外部租赁期利率
     * @param array $plan 还租计划表
     * @return float
     */
    protected function getOutsideIrr($plan)
    {
        $flow = [];
        for ($i = 0; $i <= $this->issues; $i++) {
            $flow[$i] = $plan[$i]['nominal_cash_flow'];
        }
        return Financial::irr($flow);
    }

    /**
     * 获取内部租赁期利率
     * @param array $plan 还租计划表
     * @return float
     */
    protected function getInsideIrr($plan)
    {
        $flow = [];
        for ($i = 0; $i <= $this->issues; $i++) {
            $flow[$i] = $plan[$i]['cash_flow'];
        }
        return Financial::irr($flow);
    }
}